// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>

#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#define SHADER(Src) #Src

namespace gpu {

class GLProgramTest : public testing::Test {
protected:
    void SetUp() override { gl_.Initialize(GLManager::Options()); }

    void TearDown() override { gl_.Destroy(); }

    GLManager gl_;
};

class WebGLProgramTest : public GLProgramTest {
protected:
    void SetUp() override
    {
        GLManager::Options options;
        options.context_type = gles2::CONTEXT_TYPE_WEBGL1;
        gl_.Initialize(options);
    }
};

TEST_F(GLProgramTest, GetSetUniform)
{
    static const char* v_shader_str = SHADER(
        attribute vec4 a_vertex;
        attribute vec3 a_normal;

        uniform mat4 u_modelViewProjMatrix;

        struct MyStruct {
            int x;
            int y;
        };

        uniform MyStruct u_struct;
        uniform float u_array[4];

        varying vec3 v_normal;

        void main() {
            v_normal = a_normal;
            gl_Position = u_modelViewProjMatrix * a_vertex + vec4(u_struct.x, u_struct.y, 0, 1) + vec4(u_array[0], u_array[1], u_array[2], u_array[3]);
        });
    static const char* f_shader_str = SHADER(
        varying mediump vec3 v_normal;

        void main() {
            gl_FragColor = vec4(v_normal / 2.0 + vec3(0.5), 1);
        });

    // Load the program.
    GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
    glUseProgram(program);
    // Relink program.
    glLinkProgram(program);

    // These tests will fail on NVidia if not worked around by
    // command buffer.
    GLint location_sx = glGetUniformLocation(program, "u_struct.x");
    GLint location_array_0 = glGetUniformLocation(program, "u_array[0]");

    glUniform1i(location_sx, 3);
    glUniform1f(location_array_0, 123);

    GLint int_value = 0;
    GLfloat float_value = 0;

    glGetUniformiv(program, location_sx, &int_value);
    EXPECT_EQ(3, int_value);
    glGetUniformfv(program, location_array_0, &float_value);
    EXPECT_EQ(123.0f, float_value);

    GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(GLProgramTest, NewShaderInCurrentProgram)
{
    static const char* v_shader_str = SHADER(
        attribute vec4 a_position;
        void main() {
            gl_Position = a_position;
        });
    static const char* f_red_shader_str = SHADER(
        void main() {
            gl_FragColor = vec4(1, 0, 0, 1);
        });
    static const char* f_blue_shader_str = SHADER(
        void main() {
            gl_FragColor = vec4(0, 0, 1, 1);
        });

    // Load the program.
    GLuint vs = GLTestHelper::LoadShader(GL_VERTEX_SHADER, v_shader_str);
    GLuint fs = GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, f_red_shader_str);
    GLuint program = GLTestHelper::SetupProgram(vs, fs);
    glUseProgram(program);
    glShaderSource(fs, 1, &f_blue_shader_str, NULL);
    glCompileShader(fs);
    glLinkProgram(program);
    // We specifically don't call UseProgram again.
    GLuint position_loc = glGetAttribLocation(program, "a_position");
    GLTestHelper::SetupUnitQuad(position_loc);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    uint8 expected_color[] = {
        0,
        0,
        255,
        255,
    };
    EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, expected_color));
    GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(GLProgramTest, ShaderLengthSpecified)
{
    const std::string valid_shader_str = SHADER(
        attribute vec4 a_position;
        void main() {
            gl_Position = a_position;
        });

    const std::string invalid_shader = valid_shader_str + "invalid suffix";

    // Compiling invalid program should fail.
    const char* invalid_shader_strings[] = { invalid_shader.c_str() };
    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, invalid_shader_strings, NULL);
    glCompileShader(vs);

    GLint compile_state = 0;
    glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_state);
    EXPECT_EQ(GL_FALSE, compile_state);

    // Compiling program cutting off invalid parts should succeed.
    const GLint lengths[] = { valid_shader_str.length() };
    glShaderSource(vs, 1, invalid_shader_strings, lengths);
    glCompileShader(vs);
    glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_state);
    EXPECT_EQ(GL_TRUE, compile_state);
}

TEST_F(GLProgramTest, UniformsInCurrentProgram)
{
    static const char* v_shader_str = SHADER(
        attribute vec4 a_position;
        void main() {
            gl_Position = a_position;
        });
    static const char* f_shader_str = SHADER(
        precision mediump float;
        uniform vec4 u_color;
        void main() {
            gl_FragColor = u_color;
            ;
        });

    // Load the program.
    GLuint program = GLTestHelper::LoadProgram(v_shader_str, f_shader_str);
    glUseProgram(program);

    // Relink.
    glLinkProgram(program);

    // This test will fail on NVidia Linux if not worked around.
    GLint color_location = glGetUniformLocation(program, "u_color");
    glUniform4f(color_location, 0.0f, 0.0f, 1.0f, 1.0f);

    // We specifically don't call UseProgram again.
    GLuint position_loc = glGetAttribLocation(program, "a_position");
    GLTestHelper::SetupUnitQuad(position_loc);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    uint8 expected_color[] = {
        0,
        0,
        255,
        255,
    };
    EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, expected_color));
    GLTestHelper::CheckGLError("no errors", __LINE__);
}

TEST_F(WebGLProgramTest, DeferCompileWithExt)
{
    // This test must have extensions enabled.
    gles2::ContextGroup* context_group = gl_.decoder()->GetContextGroup();
    gles2::FeatureInfo* feature_info = context_group->feature_info();
    const gles2::FeatureInfo::FeatureFlags& flags = feature_info->feature_flags();
    if (!flags.ext_frag_depth)
        return;

    static const char* v_shdr_str = R"(
      attribute vec4 vPosition;
      void main()
      {
        gl_Position = vPosition;
      }
  )";
    static const char* f_shdr_str = R"(
      #extension GL_EXT_frag_depth : enable
      void main()
      {
        gl_FragDepthEXT = 1.0;
      }
  )";

    // Extension is disabled by default and be sure shader does not compile.
    GLuint vs_bad = GLTestHelper::CompileShader(GL_VERTEX_SHADER, v_shdr_str);
    GLuint fs_bad = GLTestHelper::CompileShader(GL_FRAGMENT_SHADER, f_shdr_str);
    GLuint program_bad = GLTestHelper::LinkProgram(vs_bad, fs_bad);
    GLint linked_bad = 0;
    glGetProgramiv(program_bad, GL_LINK_STATUS, &linked_bad);
    EXPECT_EQ(0, linked_bad);

    // linking bad compilations with extension enabled should still not link.
    glRequestExtensionCHROMIUM("GL_EXT_frag_depth");
    program_bad = GLTestHelper::LinkProgram(vs_bad, fs_bad);
    linked_bad = 0;
    glGetProgramiv(program_bad, GL_LINK_STATUS, &linked_bad);
    EXPECT_EQ(0, linked_bad);

    // Be sure extension was actually turned on by recompiling.
    GLuint vs_good = GLTestHelper::LoadShader(GL_VERTEX_SHADER, v_shdr_str);
    GLuint fs_good = GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, f_shdr_str);
    GLuint program_good = GLTestHelper::SetupProgram(vs_good, fs_good);
    EXPECT_NE(0u, program_good);
}

TEST_F(GLProgramTest, DeleteAttachedShaderLinks)
{
    static const char* v_shdr_str = R"(
      attribute vec4 vPosition;
      void main()
      {
        gl_Position = vPosition;
      }
  )";
    static const char* f_shdr_str = R"(
      void main()
      {
        gl_FragColor = vec4(1, 1, 1, 1);
      }
  )";

    // Compiling the shaders, attaching, then deleting before linking should work.
    GLuint vs = GLTestHelper::CompileShader(GL_VERTEX_SHADER, v_shdr_str);
    GLuint fs = GLTestHelper::CompileShader(GL_FRAGMENT_SHADER, f_shdr_str);

    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    glDeleteShader(vs);
    glDeleteShader(fs);

    glLinkProgram(program);

    GLint linked = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    EXPECT_NE(0, linked);
}

} // namespace gpu
